// // Copyright (c) 2009 All Right Reserved // // vl // // 2009-01-01 // Contains ... using System; using System.Diagnostics.Contracts; using System.Globalization; using System.Text; using System.Xml.Serialization; using LargoCommon.Abstract; using LargoCommon.Interfaces; namespace LargoCommon.Music { /// Harmonic interval. /// Musical Interval represents one interval i.e. acoustic relation of two tones. /// Some characteristics (continuity, impulse, potential influence, Similarity,..) /// are assigned to interval. [Serializable] [XmlRoot] public class MusicalInterval : IComparable, IHarmonic { #region Constants /// Natural logarithm of 2. protected const float Log2 = 0.693147181f; /// Inverted Logarithm(2). protected const float InvLog2 = 1.442695041f; /// Tolerance of continuity. protected const float ContinuityTolerance = 1.016F; #endregion #region Fields /// /// The formal behavior /// private readonly BindingBehavior formalBehavior; /// /// The real behavior /// private readonly BindingBehavior realBehavior; /// Formal properties. private float? frmPotentialInfluence, frmSimilarity; /// Interval ratio. private float? ratio; #endregion #region Constructors /// /// Initializes a new instance of the MusicalInterval class. Serializable. /// public MusicalInterval() { this.formalBehavior = new BindingBehavior { Continuity = null, Impulse = null }; this.frmPotentialInfluence = null; this.frmSimilarity = null; this.realBehavior = new BindingBehavior { Continuity = null, Impulse = null }; } /// Initializes a new instance of the MusicalInterval class. /// Harmonic system. /// Fist element of system. /// Second element of system. public MusicalInterval(HarmonicSystem harmonicSystem, byte elementFrom, byte elementTo) : this() { Contract.Requires(harmonicSystem != null); //// if (harmonicSystem == null) { return; } this.HarmonicSystem = harmonicSystem; this.HarmonicOrder = harmonicSystem.Order; this.SystemLength = elementTo - elementFrom; this.ratio = null; this.Weight = 1.0f; // SetProperties(); this.FormalLength = GeneralSystem.FormalLength(this.HarmonicOrder, this.SystemLength); } /// Initializes a new instance of the MusicalInterval class. /// Harmonic system. /// First musical pitch. /// Second musical pitch. public MusicalInterval(HarmonicSystem harmonicSystem, MusicalPitch givenPitch1, MusicalPitch givenPitch2) : this() { Contract.Requires(harmonicSystem != null); //// if (harmonicSystem == null) { return; } this.HarmonicSystem = harmonicSystem; this.HarmonicOrder = harmonicSystem.Order; this.ratio = null; this.SystemLength = givenPitch2?.IntervalFrom(givenPitch1) ?? 0; this.Weight = 1.0f; if (givenPitch1 != null) { this.SystemAltitude = givenPitch1.SystemAltitude; } this.FormalLength = GeneralSystem.FormalLength(this.HarmonicOrder, this.SystemLength); //// SetProperties(); } /// Initializes a new instance of the MusicalInterval class. /// Harmonic system. /// First melodic tone. /// Second melodic tone. public MusicalInterval(HarmonicSystem harmonicSystem, MusicalTone tone1, MusicalTone tone2) : this() { Contract.Requires(harmonicSystem != null); Contract.Requires(tone1 != null); Contract.Requires(tone2 != null); //// if (harmonicSystem == null) { return; } //// if (tone1 == null || tone2 == null) { //// throw new ArgumentException("Tone of interval must not be null."); } this.SystemLength = 0; this.HarmonicSystem = harmonicSystem; this.HarmonicOrder = harmonicSystem.Order; this.ratio = null; this.Weight = tone1.Weight + tone2.Weight; //// 0.5f * //// this.Weight = 1.0f; (when lower time of composing needed) //// Time optimized //// MusicalPitch pitch1 = tone1.Pitch; //// MusicalPitch pitch2 = tone2.Pitch; if (tone1.Pitch != null) { //// this.SystemAltitude = tone1.Pitch.SystemAltitude; if (tone2.Pitch != null) { //// Time optimized (== pitch2.IntervalFrom(pitch1);) this.SystemLength = tone2.Pitch.SystemAltitude - tone1.Pitch.SystemAltitude; //// this.ratio = this.HarmonicSystem.RatioForInterval(this.SysLength); } } this.FormalLength = GeneralSystem.FormalLength(this.HarmonicOrder, this.SystemLength); //// Time optimization var formalInterval = this.HarmonicSystem.Intervals[this.FormalLength]; ////GetFormalInterval(this.FormalLength); if (formalInterval == null) { return; } this.formalBehavior.Continuity = formalInterval.FormalContinuity; this.formalBehavior.Impulse = formalInterval.FormalImpulse; this.frmPotentialInfluence = formalInterval.FormalPotentialInfluence; this.frmSimilarity = formalInterval.FormalSimilarity; //// SetProperties(); } #endregion #region Basic properties /// Gets inner balance. /// Property description. public static float FormalBalance => 1f; /// Gets or sets ratio of frequencies. /// Property description. public HarmonicSystem HarmonicSystem { get; set; } /// Gets or sets the string of musical symbols. /// Property description. [XmlAttribute] public string ToneSchema { get; set; } /// Gets or sets ratio of frequencies. /// Property description. public byte HarmonicOrder { get; set; } /// Gets or sets altitude from zero level. /// Property description. public int SystemAltitude { get; set; } /// Gets or sets ratio of frequencies. /// Property description. public int SystemLength { get; set; } /// Gets or sets ratio of frequencies. /// Property description. public byte FormalLength { get; set; } /// Gets or sets ratio of frequencies. /// Property description. public float Ratio { get { if (this.ratio == null) { this.ratio = this.HarmonicSystem.RatioForInterval(this.SystemLength); } return (float)this.ratio; } set => this.ratio = value; } /// Gets or sets tone weight (lower tones are heavier). /// Property description. public float Weight { get; set; } /// Gets tone weight (lower tones are heavier). /// Property description. public float MeanWeight => this.Weight / 2; #endregion #region Formal properties /// /// Gets or sets the formal energy. /// /// Property description. public HarmonicBehavior FormalEnergy { get; set; } /// Gets value of formal impulse. /// Property description. public float FormalImpulse { get { if (this.formalBehavior.Impulse == null) { this.formalBehavior.Impulse = this.FImpulse; } return (float)this.formalBehavior.Impulse; } } /// Gets value of formal continuity. /// Property description. public float FormalContinuity { get { if (this.formalBehavior.Continuity == null) { this.formalBehavior.Continuity = this.FContinuity; } return (float)this.formalBehavior.Continuity; } } /// Gets potential influence of the given interval. /// Property description. public float FormalPotentialInfluence { get { if (this.frmPotentialInfluence == null) { this.frmPotentialInfluence = this.FPotentialInfluence; } return (float)this.frmPotentialInfluence; } } /// Gets similarity of the given interval. /// Property description. public float FormalSimilarity { get { if (this.frmSimilarity == null) { this.frmSimilarity = FSimilarity(this.Ratio); } return (float)this.frmSimilarity; } } /// Gets Real impulse. /// Property description. /// Returns value. public float RealImpulse { get { if (this.realBehavior.Impulse == null) { //// 2014/1 was this.FormalImpulse * this.Weight this.realBehavior.Impulse = this.FormalImpulse * (1 + (this.Weight / 5)); } return (float)this.realBehavior.Impulse; } } /// Gets Real continuity. /// Property description. /// Returns value. public float RealContinuity { get { if (this.realBehavior.Continuity == null) { //// 2014/1 was this.FormalImpulse * this.Weight this.realBehavior.Continuity = this.FormalContinuity * (1 + (this.Weight / 5)); } return (float)this.realBehavior.Continuity; } } #endregion #region Formal properties /// Gets value of formal continuity. /// General musical property. /// Returns value. public float FContinuity { get { var value = ComputeFContinuity(this.Ratio); return value; } } /// Gets formal impulse. /// General musical property. /// Returns value. public float FImpulse { get { var value = ComputeFImpulse(this.Ratio); return value; } } /// Gets potential influence of the given interval. /// General musical property. /// Returns value. public float FPotentialInfluence { get { var vi = this.FImpulse; var vc = this.FContinuity; var value = Math.Abs(vc) - (DefaultValue.HalfUnit * vi); return value; } } #endregion #region Static operators //// TICS rule 7@526: Reference types should not override the equality operator (==) //// public static bool operator ==(MusicalInterval interval1, MusicalInterval interval2) { return object.Equals(interval1, interval2); } //// public static bool operator !=(MusicalInterval interval1, MusicalInterval interval2) { return !object.Equals(interval1, interval2); } //// but TICS rule 7@530: Class implements interface 'IComparable' but does not implement '==' and '!='. /// /// Implements the operator <. /// /// The interval1. /// The interval2. /// /// Returns value. /// public static bool operator <(MusicalInterval interval1, MusicalInterval interval2) { if (interval1 != null && interval2 != null) { return interval1.SystemAltitude < interval2.SystemAltitude; } return false; } /// /// Implements the operator >. /// /// The interval1. /// The interval2. /// /// Returns value. /// public static bool operator >(MusicalInterval interval1, MusicalInterval interval2) { if (interval1 != null && interval2 != null) { return interval1.SystemAltitude > interval2.SystemAltitude; } return false; } /// /// Implements the operator <=. /// /// The interval1. /// The interval2. /// /// Returns value. /// public static bool operator <=(MusicalInterval interval1, MusicalInterval interval2) { if (interval1 != null && interval2 != null) { return interval1.SystemAltitude <= interval2.SystemAltitude; } return false; } /// /// Implements the operator >=. /// /// The interval1. /// The interval2. /// /// Returns value. /// public static bool operator >=(MusicalInterval interval1, MusicalInterval interval2) { if (interval1 != null && interval2 != null) { return interval1.SystemAltitude >= interval2.SystemAltitude; } return false; } #endregion #region Static methods - abstract /// Logarithmic value of the formal interval ratio. /// Interval ratio. /// Returns value. public static float FormalLog(float ratio) { var slog = (float)(Math.Log(ratio) * InvLog2); var flog = slog - (float)Math.Floor(slog); return flog; } /// Value of the formal interval ratio, e.g. 1.5 for the quint. /// Interval ratio. /// Returns value. public static float FormalRatio(float ratio) { var flog = FormalLog(ratio); var fr = (float)Math.Pow(2, flog); return fr; } /// Logarithmic value of the smaller formal interval, e.g. quart not quint. /// Interval ratio. /// Returns value. public static float SharpLog(float ratio) { var flog = FormalLog(ratio); var sharpLog = Math.Min(flog, 1 - flog); return sharpLog; } /// Value of the smaller formal interval, e.g. quart not quint. /// Interval ratio. /// Returns value. public static float SharpRatio(float ratio) { var sharpLog = SharpLog(ratio); var sharpRatio = (float)Math.Pow(2, sharpLog); return sharpRatio; } /// Returns Similarity of the given interval. /// Interval ratio. /// Returns value. public static float FSimilarity(float ratio) { const float tolerance = 0.05f; var fr = FormalRatio(ratio); float value = Math.Abs(fr - 1) < tolerance ? +1 : 0; return value; } /// Returns Similarity of the given interval. /// Formal distance. /// Returns value. public static float GuessSonanceValue(int formalDistance) { const float influenceOfFifths = 0.1f; const float influenceOfThirds = 0.1f; const float influenceOfSeconds = -0.3f; const float influenceOfHalftones = -0.5f; //// The tones consonant with other tones have higher values float value = 0; formalDistance = formalDistance > short.MinValue ? Math.Abs(formalDistance) : short.MinValue; if (formalDistance == (byte)IntervalType.Fourth || formalDistance == (byte)IntervalType.Fifth) { value += influenceOfFifths; } if (formalDistance == (byte)IntervalType.MinorThird || formalDistance == (byte)IntervalType.MajorThird) { value += influenceOfThirds; } if (formalDistance == (byte)IntervalType.Second) { value += influenceOfSeconds; } if (formalDistance == (byte)IntervalType.Halftone) { value += influenceOfHalftones; } return value; } #endregion #region Static methods - formal properties /// Formal impulse. /// Interval ratio. /// Returns value. public static float ComputeFImpulse(float ratio) { const float tolerance = 0.01f; const float fi1 = 12.0f; //// (float)(22.17f / Math.Exp(24 * SharpLog((float)(1.05946)))); const float definedBase = 22.17f; const float definedMultiple = 24.0f; //// 2006/06 - simplified, float mRatio = medianRatio(ratio); var sharpLog = SharpLog(ratio); //// float r = (float)(24.0*Math.Log(mRatio)/this.Log2); var r = definedMultiple * sharpLog; var expr = Math.Exp(r); var result = !MathSupport.EqualNumbers((float)expr, 0.00f, tolerance) ? (float)(definedBase * r * r / expr) : 0f; var formalValue = (float)Math.Round((result / fi1) * 100.0f, 3); return formalValue < 100f ? formalValue : 100f; } /// Value of formal continuity. /// Interval ratio. /// Returns value. public static float ComputeFContinuity(float ratio) { const float ratioC1 = 1.500000F; const float inverseC1 = 1.333333F; const float ratioC2 = 1.250000F; const float inverseC2 = 1.600000F; const float ratioC3 = 1.750000F; const float inverseC3 = 1.142857F; var fr = FormalRatio(ratio); var formalValue = MathSupport.EqualNumbersRational(fr, ratioC1, ContinuityTolerance) ? -HarmonicSystem.C1 : 0.0F; if (MathSupport.EqualNumbersRational(fr, inverseC1, ContinuityTolerance)) { // 4.0F/3 formalValue = HarmonicSystem.C1; } else { if (MathSupport.EqualNumbersRational(fr, ratioC2, ContinuityTolerance)) { // 5.0F/4 formalValue = -HarmonicSystem.C2; } else { if (MathSupport.EqualNumbersRational(fr, inverseC2, ContinuityTolerance)) { // 8.0F/5 formalValue = HarmonicSystem.C2; } else { if (MathSupport.EqualNumbersRational(fr, ratioC3, ContinuityTolerance)) { // 7.0F/4 formalValue = -HarmonicSystem.C3; } else { if (MathSupport.EqualNumbersRational(fr, inverseC3, ContinuityTolerance)) { // 8.0F/7 formalValue = HarmonicSystem.C3; } } } } } return formalValue / HarmonicSystem.C1 * 100.0F; } /// Returns potential influence of the given interval. /// Interval ratio. /// Returns value. public static float ComputeFPotentialInfluence(float ratio) { var vi = ComputeFImpulse(ratio); //// see also FImpulse var vc = ComputeFContinuity(ratio); //// see also FContinuity var value = Math.Abs(vc) - (DefaultValue.HalfUnit * vi); return value; } #endregion #region Comparison /// Support of sorting according to interval ratio. /// Object to be compared. /// Returns value. public int CompareTo(object obj) { //// This kills the DataGrid //// throw new ArgumentException("Object is not a MusicalInterval"); return obj is MusicalInterval mi ? this.Ratio.CompareTo(mi.Ratio) : 0; } /// Test of equality. /// Object to be compared. /// Returns value. public override bool Equals(object obj) { //// check null (this pointer is never null in C# methods) if (object.ReferenceEquals(obj, null)) { return false; } if (object.ReferenceEquals(this, obj)) { return true; } if (this.GetType() != obj.GetType()) { return false; } return this.CompareTo(obj) == 0; } /// Support of comparison. /// Returns value. public override int GetHashCode() { return this.Ratio.GetHashCode(); } #endregion #region Public methods /// /// Value Of Property. /// /// Musical property. /// Returns value. public float ValueOfProperty(GenProperty property) { float value; switch (property) { case GenProperty.InnerContinuity: value = this.FormalContinuity; break; case GenProperty.InnerImpulse: value = this.FormalImpulse; break; case GenProperty.FormalPotentialInfluence: value = this.FormalPotentialInfluence; break; case GenProperty.RealContinuity: value = this.RealContinuity; break; case GenProperty.RealImpulse: value = this.RealImpulse; break; default: value = 0; break; } return value; } #endregion #region String representation /// String representation of the object. /// Returns value. public override string ToString() { var s = new StringBuilder(); s.Append( $"{string.Format(CultureInfo.CurrentCulture.NumberFormat, "{0,6}", this.Ratio)} ({string.Format(CultureInfo.CurrentCulture.NumberFormat, "{0,6}", this.Weight)})\t"); //// s.Append(this.StringOfProperties()); return s.ToString(); } #endregion } }